package ca.josephroque.bowlingcompanion.fragment; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.Switch; import android.widget.TextView; import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineDataSet; import com.github.mikephil.charting.formatter.DefaultValueFormatter; import com.github.mikephil.charting.formatter.DefaultYAxisValueFormatter; import com.github.mikephil.charting.formatter.ValueFormatter; import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; import java.lang.ref.WeakReference; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import ca.josephroque.bowlingcompanion.Constants; import ca.josephroque.bowlingcompanion.MainActivity; import ca.josephroque.bowlingcompanion.R; import ca.josephroque.bowlingcompanion.database.Contract; import ca.josephroque.bowlingcompanion.database.Contract.FrameEntry; import ca.josephroque.bowlingcompanion.database.Contract.GameEntry; import ca.josephroque.bowlingcompanion.database.Contract.SeriesEntry; import ca.josephroque.bowlingcompanion.database.Contract.LeagueEntry; import ca.josephroque.bowlingcompanion.database.DatabaseHelper; import ca.josephroque.bowlingcompanion.theme.Theme; import ca.josephroque.bowlingcompanion.utilities.DateUtils; import ca.josephroque.bowlingcompanion.utilities.DisplayUtils; import ca.josephroque.bowlingcompanion.utilities.Score; import ca.josephroque.bowlingcompanion.utilities.StatUtils; /** * Created by Joseph Roque on 15-07-20. Manages the UI to display information about the stats in a graph for a * particular bowler */ public class StatsGraphFragment extends Fragment implements Theme.ChangeableTheme { /** Identifies output from this class in Logcat. */ @SuppressWarnings("unused") private static final String TAG = "StatsGraphFragment"; /** Represents the stat category being displayed. */ private static final String ARG_STAT_CATEGORY = "arg_stat_cat"; /** Represents the stat index being displayed. */ private static final String ARG_STAT_INDEX = "arg_stat_index"; /** Represents whether the stat calculation is accumulated over time. */ private static final String ARG_STAT_ACCUMULATE = "arg_stat_accumulate"; /** LineChart to display statistics over time. */ private LineChart mLineChartStats; /** TextView to display name of statistic. */ private TextView mTextViewStat; /** Switch to allow user to set stats show as accumulated over time, or be week by week. */ private Switch mSwitchAccumulate; /** Provides context to the user of the purpose of {@code mSwitchAccumulate}. */ private TextView mTextViewAccumulate; /** Contains views for navigating through stats. */ private RelativeLayout mStatNavigation; /** Image view for user to advance to next stat graph. */ private ImageView mImageViewNextStat; /** Image view for user to backtrack to previous stat graph. */ private ImageView mImageViewPrevStat; /** Text view for user to advance to next stat graph. */ private TextView mTextViewNextStat; /** Text view for user to backtrack to previous stat graph. */ private TextView mTextViewPrevStat; /** The category of the stat being displayed. */ private int mStatCategory; /** The index of the stat being displayed. */ private int mStatIndex; /** Indicates if stats should be accumulated over time, or be calculated week by week. */ private boolean mStatAccumulate = false; /** Indicates if Headpin + 2 should be counted as Headpins. */ private boolean mCountH2 = false; /** Indicates if Split + 2 should be counted as Splits. */ private boolean mCountS2 = false; /** * Creates a new instance of {@code StatsGraphFragment} with the parameters provided. * * @param statCategory category of stat displayed in graph * @param statIndex index of stat displayed in graph * @return a new instance of StatsGraphFragment */ public static StatsGraphFragment newInstance(int statCategory, int statIndex) { StatsGraphFragment fragment = new StatsGraphFragment(); Bundle args = new Bundle(); args.putInt(ARG_STAT_CATEGORY, statCategory); args.putInt(ARG_STAT_INDEX, statIndex); fragment.setArguments(args); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View rootView = inflater.inflate(R.layout.fragment_stats_graph, container, false); if (savedInstanceState != null) { mStatCategory = savedInstanceState.getInt(ARG_STAT_CATEGORY, 0); mStatIndex = savedInstanceState.getInt(ARG_STAT_INDEX, 0); mStatAccumulate = savedInstanceState.getBoolean(ARG_STAT_ACCUMULATE, false); } else { Bundle arguments = getArguments(); mStatCategory = arguments.getInt(ARG_STAT_CATEGORY, 0); mStatIndex = arguments.getInt(ARG_STAT_INDEX, 0); } mLineChartStats = (LineChart) rootView.findViewById(R.id.chart_stats); mTextViewStat = (TextView) rootView.findViewById(R.id.tv_stat_name); mSwitchAccumulate = (Switch) rootView.findViewById(R.id.switch_stat_accumulate); mTextViewAccumulate = (TextView) rootView.findViewById(R.id.tv_stat_accumulate); setupNavigation(rootView); setupAccumulateSwitch(); if (mStatAccumulate) { mSwitchAccumulate.setChecked(true); mTextViewAccumulate.setText(R.string.text_stats_accumulate); } else { mSwitchAccumulate.setChecked(false); mTextViewAccumulate.setText(R.string.text_stats_by_week); } return rootView; } @Override public void onResume() { super.onResume(); if (getActivity() != null) { MainActivity mainActivity = (MainActivity) getActivity(); mainActivity.setFloatingActionButtonState(0, 0); mainActivity.setDrawerState(false); // Check if the user has opted to count headpins + 2 as headpins or not, as well as splits + 2 as splits SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mainActivity); mCountH2 = preferences.getBoolean(Constants.KEY_COUNT_H2_AS_H, false); mCountS2 = preferences.getBoolean(Constants.KEY_COUNT_S2_AS_S, false); // Checks what type of stats should be displayed, depending // on what data is available in the parent activity at the time byte statsToLoad; int titleToSet; if (mainActivity.getLeagueId() == -1) { titleToSet = R.string.title_stats_bowler; statsToLoad = StatUtils.LOADING_BOWLER_STATS; } else { titleToSet = R.string.title_stats_league; statsToLoad = StatUtils.LOADING_LEAGUE_STATS; } mainActivity.setActionBarTitle(titleToSet, true); new LoadStatsGraphTask(this).execute(statsToLoad); } updateTheme(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(ARG_STAT_CATEGORY, mStatCategory); outState.putInt(ARG_STAT_INDEX, mStatIndex); outState.putBoolean(ARG_STAT_ACCUMULATE, mStatAccumulate); } @Override public void updateTheme() { if (mTextViewStat != null) mTextViewStat.setBackgroundColor(Theme.getHighlightThemeColor()); if (mStatNavigation != null) mStatNavigation.setBackgroundColor(Theme.getPrimaryThemeColor()); } /** * Sets up on click listeners for switch between accumulated or weekly stats. */ private void setupAccumulateSwitch() { View.OnClickListener switchListener = new View.OnClickListener() { @Override public void onClick(View v) { loadNewStat(!mStatAccumulate); } }; mSwitchAccumulate.setOnClickListener(switchListener); mTextViewAccumulate.setOnClickListener(switchListener); } /** * Sets on click listeners for next / prev stat buttons and gets reference for {@code mStatNavigation}. * * @param rootView root view of fragment */ private void setupNavigation(View rootView) { mStatNavigation = (RelativeLayout) rootView.findViewById(R.id.fl_stat_navigation); mImageViewNextStat = (ImageView) rootView.findViewById(R.id.iv_next_stat); mTextViewNextStat = (TextView) rootView.findViewById(R.id.tv_next_stat); mImageViewPrevStat = (ImageView) rootView.findViewById(R.id.iv_prev_stat); mTextViewPrevStat = (TextView) rootView.findViewById(R.id.tv_prev_stat); View.OnClickListener nextClickListener = new View.OnClickListener() { @Override public void onClick(View v) { int nextStatCategory = mStatCategory; int nextStatIndex = mStatIndex + 1; try { StatUtils.getStatName(nextStatCategory, nextStatIndex, false); } catch (IllegalArgumentException ex) { nextStatCategory++; nextStatIndex = 0; } if (nextStatCategory > StatUtils.STAT_CATEGORY_OVERALL) return; mStatCategory = nextStatCategory; mStatIndex = nextStatIndex; loadNewStat(false); } }; View.OnClickListener prevClickListener = new View.OnClickListener() { @Override public void onClick(View v) { int nextStatCategory = mStatCategory; int nextStatIndex = mStatIndex - 1; if (nextStatIndex < 0) { nextStatCategory--; if (nextStatCategory < 0) return; nextStatIndex = 0; while (true) { try { StatUtils.getStatName(nextStatCategory, nextStatIndex, false); } catch (IllegalArgumentException ex) { break; } nextStatIndex++; } nextStatIndex -= 1; } mStatCategory = nextStatCategory; mStatIndex = nextStatIndex; loadNewStat(false); } }; mImageViewNextStat.setOnClickListener(nextClickListener); mTextViewNextStat.setOnClickListener(nextClickListener); mImageViewPrevStat.setOnClickListener(prevClickListener); mTextViewPrevStat.setOnClickListener(prevClickListener); } /** * Loads a new stat to the graph. * * @param accumulate true if the stat should be accumulated over time. */ private void loadNewStat(boolean accumulate) { byte statsToLoad; MainActivity mainActivity = (MainActivity) getActivity(); if (mainActivity == null) return; if (mainActivity.getLeagueId() == -1) statsToLoad = StatUtils.LOADING_BOWLER_STATS; else statsToLoad = StatUtils.LOADING_LEAGUE_STATS; mStatAccumulate = accumulate; mSwitchAccumulate.setChecked(mStatAccumulate); mTextViewAccumulate.setText((mStatAccumulate) ? R.string.text_stats_accumulate : R.string.text_stats_by_week); new LoadStatsGraphTask(StatsGraphFragment.this).execute(statsToLoad); } /** * Enables or disables UI elements. * * @param enable true to enable, false to disable */ private void setUIEnabled(boolean enable) { mSwitchAccumulate.setEnabled(enable); mTextViewAccumulate.setEnabled(enable); mImageViewNextStat.setEnabled(enable); mTextViewNextStat.setEnabled(enable); mImageViewPrevStat.setEnabled(enable); mTextViewPrevStat.setEnabled(enable); } /** * Loads data from the database and calculates relevant stats depending on which type of stats are being loaded. */ private static final class LoadStatsGraphTask extends AsyncTask<Byte, Void, LineData> { /** Weak reference to the parent fragment. */ private final WeakReference<StatsGraphFragment> mFragment; /** Indicates the type of stats being loaded (i.e. for a bowler or a league). */ private byte mStatsToLoad; /** * Assigns a weak reference to the parent fragment. * * @param fragment parent fragment */ private LoadStatsGraphTask(StatsGraphFragment fragment) { mFragment = new WeakReference<>(fragment); } @Override protected void onPreExecute() { StatsGraphFragment fragment = mFragment.get(); if (fragment == null) return; fragment.setUIEnabled(false); int visibilityNext = (fragment.mStatIndex == StatUtils.STAT_NUMBER_OF_GAMES && fragment.mStatCategory == StatUtils.STAT_CATEGORY_OVERALL) ? View.GONE : View.VISIBLE; int visibilityPrev = (fragment.mStatIndex == 0 && fragment.mStatCategory == 0) ? View.GONE : View.VISIBLE; fragment.mImageViewNextStat.setVisibility(visibilityNext); fragment.mTextViewNextStat.setVisibility(visibilityNext); fragment.mImageViewPrevStat.setVisibility(visibilityPrev); fragment.mTextViewPrevStat.setVisibility(visibilityPrev); } @Override protected LineData doInBackground(Byte... statsToLoad) { StatsGraphFragment fragment = mFragment.get(); if (fragment == null || !fragment.isAdded()) return null; MainActivity mainActivity = (MainActivity) fragment.getActivity(); if (mainActivity == null) return null; MainActivity.waitForSaveThreads(new WeakReference<>(mainActivity)); mStatsToLoad = statsToLoad[0]; Cursor cursor; switch (mStatsToLoad) { case StatUtils.LOADING_LEAGUE_STATS: cursor = fragment.getBowlerOrLeagueCursor(true); break; case StatUtils.LOADING_BOWLER_STATS: cursor = fragment.getBowlerOrLeagueCursor(false); break; default: throw new IllegalArgumentException("invalid value for toLoad: " + mStatsToLoad + ". must be between 0 and 1 (inclusive)"); } List<Entry> listChanceEntries = new ArrayList<>(); List<Entry> listSuccessEntries = new ArrayList<>(); List<String> listLabels = new ArrayList<>(); compileGraphData(fragment, cursor, listChanceEntries, listSuccessEntries, listLabels); if (!cursor.isClosed()) cursor.close(); List<ILineDataSet> datasets = new ArrayList<>(); ValueFormatter valueFormatter = new DefaultValueFormatter(0); LineDataSet datasetSuccess = new LineDataSet(listSuccessEntries, StatUtils.getStatName(fragment.mStatCategory, fragment.mStatIndex, false)); LineDataSet datasetChances = null; String statChanceName = StatUtils.getStatName(fragment.mStatCategory, fragment.mStatIndex, true); if (statChanceName != null) datasetChances = new LineDataSet(listChanceEntries, statChanceName); if (datasetChances != null) { datasetChances.setValueFormatter(valueFormatter); datasetChances.setCircleColor(DisplayUtils.getColorResource(fragment.getResources(), R.color.chance_data)); datasetChances.setColor(DisplayUtils.getColorResource(fragment.getResources(), R.color.chance_data)); datasets.add(datasetChances); } datasetSuccess.setValueFormatter(valueFormatter); datasetSuccess.setCircleColor(DisplayUtils.getColorResource(fragment.getResources(), R.color.success_data)); datasetSuccess.setColor(DisplayUtils.getColorResource(fragment.getResources(), R.color.success_data)); datasets.add(datasetSuccess); return new LineData(listLabels, datasets); } @Override protected void onPostExecute(LineData result) { StatsGraphFragment fragment = mFragment.get(); if (fragment == null || result == null) return; // Setting up UI final float textSizeBody = 14; fragment.mTextViewStat.setText(StatUtils.getStatName(fragment.mStatCategory, fragment.mStatIndex, false)); fragment.mLineChartStats.setDescription(StatUtils.getStatName(fragment.mStatCategory, fragment.mStatIndex, false)); fragment.mLineChartStats.setDescriptionTextSize(textSizeBody); fragment.mLineChartStats.getAxisLeft().setValueFormatter(new DefaultYAxisValueFormatter(0)); // Styling legend Legend legend = fragment.mLineChartStats.getLegend(); legend.setTextSize(textSizeBody); // Applying data fragment.mLineChartStats.setData(result); fragment.mLineChartStats.invalidate(); fragment.setUIEnabled(true); } /** * Invokes relevant methods for getting the graph data for a stat. * * @param fragment parent fragment * @param cursor bowler / league data * @param listChanceEntries list of data entries for chances at increasing a stat * @param listSuccessEntries list of data entries for achieved stat values * @param listLabels list of labels for x axis */ private void compileGraphData(StatsGraphFragment fragment, Cursor cursor, List<Entry> listChanceEntries, List<Entry> listSuccessEntries, List<String> listLabels) { switch (fragment.mStatCategory) { case StatUtils.STAT_CATEGORY_GENERAL: compileGeneralStats(fragment, cursor, listChanceEntries, listSuccessEntries, listLabels); break; case StatUtils.STAT_CATEGORY_FIRST_BALL: compileFirstBallStats(fragment, cursor, listChanceEntries, listSuccessEntries, listLabels); break; case StatUtils.STAT_CATEGORY_FOULS: compileFoulStats(fragment, cursor, listSuccessEntries, listLabels); break; case StatUtils.STAT_CATEGORY_PINS: compilePinStats(fragment, cursor, listSuccessEntries, listLabels); break; case StatUtils.STAT_CATEGORY_AVERAGE_BY_GAME: compileAverageStats(fragment, cursor, listSuccessEntries, listLabels); break; case StatUtils.STAT_CATEGORY_MATCH_PLAY: compileMatchPlayStats(fragment, cursor, listChanceEntries, listSuccessEntries, listLabels); break; case StatUtils.STAT_CATEGORY_OVERALL: compileOverallStats(fragment, cursor, listSuccessEntries, listLabels); break; default: throw new IllegalStateException( "invalid stat category: " + fragment.mStatCategory); } } /** * Generates line chart data for general stats. * * @param fragment parent fragment * @param cursor bowler / league data * @param listChanceEntries list of data entries for chances at increasing a stat * @param listSuccessEntries list of data entries for achieved stat values * @param listLabels list of labels for x axis */ @SuppressWarnings("CheckStyle") // I ain't even gonna bother private void compileGeneralStats(StatsGraphFragment fragment, Cursor cursor, List<Entry> listChanceEntries, List<Entry> listSuccessEntries, List<String> listLabels) { Calendar lastEntryDate = null; Calendar lastLabelDate = null; Calendar currentDate = null; DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.CANADA); boolean addLabelOnDateChange = false; int totalShotsAtMiddle = 0, middleHits = 0; int leftHits = 0, rightHits = 0; int spareChances = 0, spares = 0; int strikes = 0; int currentEntry = 0; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { Date entryDate = DateUtils.parseEntryDate(cursor.getString( cursor.getColumnIndex(SeriesEntry.COLUMN_SERIES_DATE))); if (entryDate == null) return; currentDate = DateUtils.getCalendarAtMidnight(entryDate); if (addLabelOnDateChange && currentDate.getTimeInMillis() != lastEntryDate.getTimeInMillis()) { addLabelOnDateChange = false; lastLabelDate = lastEntryDate; Entry chanceEntry; Entry successEntry; switch (fragment.mStatIndex) { case StatUtils.STAT_MIDDLE_HIT: chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(middleHits, currentEntry); break; case StatUtils.STAT_SPARE_CONVERSIONS: chanceEntry = new Entry(spareChances, currentEntry); successEntry = new Entry(spares, currentEntry); break; case StatUtils.STAT_STRIKES: chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(strikes, currentEntry); break; case StatUtils.STAT_LEFT_OF_MIDDLE: chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(leftHits, currentEntry); break; case StatUtils.STAT_RIGHT_OF_MIDDLE: chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(rightHits, currentEntry); break; default: chanceEntry = null; successEntry = null; // does nothing } if (chanceEntry != null) { listChanceEntries.add(chanceEntry); listSuccessEntries.add(successEntry); listLabels.add(dateFormat.format(lastEntryDate.getTime())); currentEntry++; } if (!fragment.mStatAccumulate) { totalShotsAtMiddle = 0; middleHits = 0; spareChances = 0; spares = 0; strikes = 0; } } if (lastLabelDate == null || lastLabelDate.getTimeInMillis() <= currentDate.getTimeInMillis() + DateUtils.MILLIS_ONE_WEEK) addLabelOnDateChange = true; lastEntryDate = currentDate; short gameScore = cursor.getShort(cursor.getColumnIndex(GameEntry.COLUMN_SCORE)); boolean gameIsManual = (cursor.getInt(cursor.getColumnIndex( GameEntry.COLUMN_IS_MANUAL)) == 1); if (gameIsManual || gameScore == 0) { cursor.moveToNext(); continue; } boolean[][] pinState = new boolean[3][5]; for (byte i = 0; i < pinState.length; i++) { pinState[i] = Score.ballIntToBoolean(cursor.getInt(cursor.getColumnIndex( FrameEntry.COLUMN_PIN_STATE[i]))); } int frameNumber = cursor.getInt(cursor.getColumnIndex( FrameEntry.COLUMN_FRAME_NUMBER)); totalShotsAtMiddle++; if (pinState[0][2]) // Count hits of headpin on first ball middleHits++; else { // Count hits left or right of headpin on first ball if (pinState[0][0] || pinState[0][1]) leftHits++; if (pinState[0][3] || pinState[0][4]) rightHits++; } if (frameNumber == Constants.LAST_FRAME) { if (Arrays.equals(pinState[0], Constants.FRAME_PINS_DOWN)) { totalShotsAtMiddle++; strikes++; if (Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) { totalShotsAtMiddle++; middleHits++; strikes++; if (Arrays.equals(pinState[2], Constants.FRAME_PINS_DOWN)) { middleHits++; strikes++; } else { if (pinState[2][0] || pinState[2][1]) leftHits++; if (pinState[2][3] || pinState[2][4]) rightHits++; } } else { if (pinState[1][0] || pinState[1][1]) leftHits++; if (pinState[1][3] || pinState[1][4]) rightHits++; spareChances++; if (Arrays.equals(pinState[2], Constants.FRAME_PINS_DOWN)) spares++; } } else { spareChances++; if (Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) { totalShotsAtMiddle++; middleHits++; spares++; if (Arrays.equals(pinState[2], Constants.FRAME_PINS_DOWN)) strikes++; } else { if (pinState[1][0] || pinState[1][1]) leftHits++; if (pinState[1][3] || pinState[1][4]) rightHits++; } } } else { if (Arrays.equals(pinState[0], Constants.FRAME_PINS_DOWN)) strikes++; else { spareChances++; if (Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) spares++; } } cursor.moveToNext(); } } if (lastEntryDate != null && (lastLabelDate == null || currentDate.getTimeInMillis() != lastLabelDate.getTimeInMillis())) { Entry chanceEntry; Entry successEntry; switch (fragment.mStatIndex) { case StatUtils.STAT_MIDDLE_HIT: chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(middleHits, currentEntry); break; case StatUtils.STAT_SPARE_CONVERSIONS: chanceEntry = new Entry(spareChances, currentEntry); successEntry = new Entry(spares, currentEntry); break; case StatUtils.STAT_STRIKES: chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(strikes, currentEntry); break; case StatUtils.STAT_LEFT_OF_MIDDLE: chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(leftHits, currentEntry); break; case StatUtils.STAT_RIGHT_OF_MIDDLE: chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(rightHits, currentEntry); break; default: chanceEntry = null; successEntry = null; // does nothing } if (chanceEntry != null) { listChanceEntries.add(chanceEntry); listSuccessEntries.add(successEntry); listLabels.add(dateFormat.format(lastEntryDate.getTime())); } } } /** * Generates line chart data for first ball stats. * * @param fragment parent fragment * @param cursor bowler / league data * @param listChanceEntries list of data entries for chances at increasing a stat * @param listSuccessEntries list of data entries for achieved stat values * @param listLabels list of labels for x axis */ @SuppressWarnings("CheckStyle") private void compileFirstBallStats(StatsGraphFragment fragment, Cursor cursor, List<Entry> listChanceEntries, List<Entry> listSuccessEntries, List<String> listLabels) { Calendar lastEntryDate = null; Calendar lastLabelDate = null; Calendar currentDate = null; DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.CANADA); boolean addLabelOnDateChange = false; int totalShotsAtMiddle = 0; //noinspection CheckStyle int[] firstBallStats = new int[20]; int currentEntry = 0; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { Date entryDate = DateUtils.parseEntryDate(cursor.getString( cursor.getColumnIndex(SeriesEntry.COLUMN_SERIES_DATE))); if (entryDate == null) return; currentDate = DateUtils.getCalendarAtMidnight(entryDate); if (addLabelOnDateChange && currentDate.getTimeInMillis() != lastEntryDate.getTimeInMillis()) { addLabelOnDateChange = false; lastLabelDate = lastEntryDate; Entry chanceEntry; Entry successEntry; if (fragment.mStatIndex % 2 == 0) { chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(firstBallStats[fragment.mStatIndex], currentEntry); } else { chanceEntry = new Entry(firstBallStats[fragment.mStatIndex - 1], currentEntry); successEntry = new Entry(firstBallStats[fragment.mStatIndex], currentEntry); } listChanceEntries.add(chanceEntry); listSuccessEntries.add(successEntry); listLabels.add(dateFormat.format(lastEntryDate.getTime())); currentEntry++; if (!fragment.mStatAccumulate) { totalShotsAtMiddle = 0; for (int i = 0; i < firstBallStats.length; i++) firstBallStats[i] = 0; } } if (lastLabelDate == null || lastLabelDate.getTimeInMillis() <= currentDate.getTimeInMillis() + DateUtils.MILLIS_ONE_WEEK) addLabelOnDateChange = true; lastEntryDate = currentDate; short gameScore = cursor.getShort(cursor.getColumnIndex(GameEntry.COLUMN_SCORE)); boolean gameIsManual = (cursor.getInt(cursor.getColumnIndex( Contract.GameEntry.COLUMN_IS_MANUAL)) == 1); if (gameIsManual || gameScore == 0) { cursor.moveToNext(); continue; } //noinspection CheckStyle boolean[][] pinState = new boolean[3][5]; for (byte i = 0; i < pinState.length; i++) { pinState[i] = Score.ballIntToBoolean(cursor.getInt(cursor.getColumnIndex( FrameEntry.COLUMN_PIN_STATE[i]))); } int frameNumber = cursor.getInt(cursor.getColumnIndex( FrameEntry.COLUMN_FRAME_NUMBER)); if (frameNumber == Constants.NUMBER_OF_FRAMES) { totalShotsAtMiddle++; int ballValue = StatUtils.getFirstBallValue(pinState[0], fragment.mCountH2, fragment.mCountS2); increaseFirstBallStat(ballValue, firstBallStats, 0); if (ballValue != 0) { if (Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) increaseFirstBallStat(ballValue, firstBallStats, 1); } else { totalShotsAtMiddle++; ballValue = StatUtils.getFirstBallValue(pinState[1], fragment.mCountH2, fragment.mCountS2); increaseFirstBallStat(ballValue, firstBallStats, 0); if (ballValue != 0) { if (Arrays.equals(pinState[2], Constants.FRAME_PINS_DOWN)) increaseFirstBallStat(ballValue, firstBallStats, 1); } else { totalShotsAtMiddle++; ballValue = StatUtils.getFirstBallValue(pinState[2], fragment.mCountH2, fragment.mCountS2); increaseFirstBallStat(ballValue, firstBallStats, 0); } } } else { totalShotsAtMiddle++; int ballValue = StatUtils.getFirstBallValue(pinState[0], fragment.mCountH2, fragment.mCountS2); increaseFirstBallStat(ballValue, firstBallStats, 0); if (ballValue != 0) { if (Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) increaseFirstBallStat(ballValue, firstBallStats, 1); } } cursor.moveToNext(); } } if (lastEntryDate != null && (lastLabelDate == null || currentDate.getTimeInMillis() != lastLabelDate.getTimeInMillis())) { Entry chanceEntry; Entry successEntry; if (fragment.mStatIndex % 2 == 0) { chanceEntry = new Entry(totalShotsAtMiddle, currentEntry); successEntry = new Entry(firstBallStats[fragment.mStatIndex], currentEntry); } else { chanceEntry = new Entry(firstBallStats[fragment.mStatIndex - 1], currentEntry); successEntry = new Entry(firstBallStats[fragment.mStatIndex], currentEntry); } listChanceEntries.add(chanceEntry); listSuccessEntries.add(successEntry); listLabels.add(dateFormat.format(lastEntryDate.getTime())); } } /** * Checks which situation has occurred by the state of the pins in ball. * * @param ball result of the pins after a ball was thrown * @param statValues stat values to update * @param offset indicates a spare was thrown and the spare count should be increased for a stat */ private void increaseFirstBallStat(int ball, int[] statValues, int offset) { if (offset > 1 || offset < 0) throw new IllegalArgumentException("Offset must be either 0 or 1: " + offset); switch (ball) { case Constants.BALL_VALUE_LEFT: statValues[StatUtils.STAT_LEFT + offset]++; break; case Constants.BALL_VALUE_RIGHT: statValues[StatUtils.STAT_RIGHT + offset]++; break; case Constants.BALL_VALUE_LEFT_CHOP: statValues[StatUtils.STAT_LEFT_CHOP + offset]++; statValues[StatUtils.STAT_CHOP + offset]++; break; case Constants.BALL_VALUE_RIGHT_CHOP: statValues[StatUtils.STAT_RIGHT_CHOP + offset]++; statValues[StatUtils.STAT_CHOP + offset]++; break; case Constants.BALL_VALUE_ACE: statValues[StatUtils.STAT_ACES + offset]++; break; case Constants.BALL_VALUE_LEFT_SPLIT: statValues[StatUtils.STAT_LEFT_SPLIT + offset]++; statValues[StatUtils.STAT_SPLIT + offset]++; break; case Constants.BALL_VALUE_RIGHT_SPLIT: statValues[StatUtils.STAT_RIGHT_SPLIT + offset]++; statValues[StatUtils.STAT_SPLIT + offset]++; break; case Constants.BALL_VALUE_HEAD_PIN: statValues[StatUtils.STAT_HEAD_PINS + offset]++; default: // does nothing } } /** * Counts the total value of pins which were left at the end of a frame on the third ball. * * @param thirdBall state of the pins after the third ball * @return total value of pins left standing */ @SuppressWarnings("CheckStyle") private int countPinsLeftStanding(boolean[] thirdBall) { int pinsLeftStanding = 0; for (int i = 0; i < thirdBall.length; i++) { if (!thirdBall[i]) { switch (i) { case 0: case 4: pinsLeftStanding += 2; break; case 1: case 3: pinsLeftStanding += 3; break; case 2: pinsLeftStanding += 5; break; default: // does nothing } } } return pinsLeftStanding; } /** * Generates line chart data for foul stats. * * @param fragment parent fragment * @param cursor bowler / league data * @param listEntries list of data entries * @param listLabels list of labels for x axis */ private void compileFoulStats(StatsGraphFragment fragment, Cursor cursor, List<Entry> listEntries, List<String> listLabels) { Calendar lastEntryDate = null; Calendar lastLabelDate = null; Calendar currentDate = null; DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.CANADA); boolean addLabelOnDateChange = false; int totalFouls = 0; int currentEntry = 0; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { Date entryDate = DateUtils.parseEntryDate(cursor.getString( cursor.getColumnIndex(SeriesEntry.COLUMN_SERIES_DATE))); if (entryDate == null) return; currentDate = DateUtils.getCalendarAtMidnight(entryDate); if (addLabelOnDateChange && currentDate.getTimeInMillis() != lastEntryDate.getTimeInMillis()) { addLabelOnDateChange = false; lastLabelDate = lastEntryDate; listEntries.add(new Entry(totalFouls, currentEntry)); listLabels.add(dateFormat.format(lastEntryDate.getTime())); currentEntry++; if (!fragment.mStatAccumulate) totalFouls = 0; } if (lastLabelDate == null || lastLabelDate.getTimeInMillis() <= currentDate.getTimeInMillis() + DateUtils.MILLIS_ONE_WEEK) addLabelOnDateChange = true; lastEntryDate = currentDate; short gameScore = cursor.getShort(cursor.getColumnIndex(GameEntry.COLUMN_SCORE)); boolean gameIsManual = (cursor.getInt(cursor.getColumnIndex( Contract.GameEntry.COLUMN_IS_MANUAL)) == 1); if (gameIsManual || gameScore == 0) { cursor.moveToNext(); continue; } String frameFouls = Score.foulIntToString(cursor.getInt(cursor.getColumnIndex( Contract.FrameEntry.COLUMN_FOULS))); //noinspection CheckStyle for (byte i = 1; i <= 3; i++) { if (frameFouls.contains(String.valueOf(i))) totalFouls++; } cursor.moveToNext(); } } if (lastEntryDate != null && (lastLabelDate == null || currentDate.getTimeInMillis() != lastLabelDate.getTimeInMillis())) { listEntries.add(new Entry(totalFouls, currentEntry)); listLabels.add(dateFormat.format(lastEntryDate.getTime())); } } /** * Generates line chart data for pin stats. * * @param fragment parent fragment * @param cursor bowler / league data * @param listEntries list of data entries * @param listLabels list of labels for x axis */ @SuppressWarnings("CheckStyle") private void compilePinStats(StatsGraphFragment fragment, Cursor cursor, List<Entry> listEntries, List<String> listLabels) { Calendar lastEntryDate = null; Calendar lastLabelDate = null; Calendar currentDate = null; DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.CANADA); boolean addLabelOnDateChange = false; int totalPinsLeft = 0, numberOfGames = 0; int currentEntry = 0; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { Date entryDate = DateUtils.parseEntryDate(cursor.getString( cursor.getColumnIndex(SeriesEntry.COLUMN_SERIES_DATE))); if (entryDate == null) return; currentDate = DateUtils.getCalendarAtMidnight(entryDate); if (addLabelOnDateChange && currentDate.getTimeInMillis() != lastEntryDate.getTimeInMillis()) { addLabelOnDateChange = false; lastLabelDate = lastEntryDate; Entry entry; if (fragment.mStatIndex == StatUtils.STAT_PINS_LEFT) entry = new Entry(totalPinsLeft, currentEntry); else if (numberOfGames > 0) entry = new Entry(totalPinsLeft / numberOfGames, currentEntry); else entry = new Entry(0, currentEntry); listEntries.add(entry); listLabels.add(dateFormat.format(lastEntryDate.getTime())); currentEntry++; if (!fragment.mStatAccumulate) { totalPinsLeft = 0; numberOfGames = 0; } } if (lastLabelDate == null || lastLabelDate.getTimeInMillis() <= currentDate.getTimeInMillis() + DateUtils.MILLIS_ONE_WEEK) addLabelOnDateChange = true; lastEntryDate = currentDate; short gameScore = cursor.getShort(cursor.getColumnIndex(GameEntry.COLUMN_SCORE)); boolean gameIsManual = (cursor.getInt(cursor.getColumnIndex( Contract.GameEntry.COLUMN_IS_MANUAL)) == 1); if (gameIsManual || gameScore == 0) { cursor.moveToNext(); continue; } numberOfGames++; //noinspection CheckStyle boolean[][] pinState = new boolean[Constants.NUMBER_OF_BALLS][Constants.NUMBER_OF_FRAMES]; for (byte i = 0; i < pinState.length; i++) { pinState[i] = Score.ballIntToBoolean(cursor.getInt(cursor.getColumnIndex( FrameEntry.COLUMN_PIN_STATE[i]))); } int frameNumber = cursor.getInt(cursor.getColumnIndex( FrameEntry.COLUMN_FRAME_NUMBER)); if (frameNumber == Constants.NUMBER_OF_FRAMES) { int ballValue = StatUtils.getFirstBallValue(pinState[0], fragment.mCountH2, fragment.mCountS2); if (ballValue != 0) { if (!Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) totalPinsLeft += countPinsLeftStanding(pinState[2]); } else { ballValue = StatUtils.getFirstBallValue(pinState[1], fragment.mCountH2, fragment.mCountS2); if (ballValue != 0) { if (!Arrays.equals(pinState[2], Constants.FRAME_PINS_DOWN)) totalPinsLeft += countPinsLeftStanding(pinState[2]); } } } else { int ballValue = StatUtils.getFirstBallValue(pinState[0], fragment.mCountH2, fragment.mCountS2); if (ballValue != 0) { if (!Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) { totalPinsLeft += countPinsLeftStanding(pinState[2]); } } } cursor.moveToNext(); } } if (lastEntryDate != null && (lastLabelDate == null || currentDate.getTimeInMillis() != lastLabelDate.getTimeInMillis())) { Entry entry; if (fragment.mStatIndex == StatUtils.STAT_PINS_LEFT) entry = new Entry(totalPinsLeft, currentEntry); else if (numberOfGames > 0) entry = new Entry(totalPinsLeft / numberOfGames, currentEntry); else entry = new Entry(0, currentEntry); listEntries.add(entry); listLabels.add(dateFormat.format(lastEntryDate.getTime())); } } /** * Generates line chart data for game average stats. * * @param fragment parent fragment * @param cursor bowler / league data * @param listEntries list of data entries * @param listLabels list of labels for x axis */ @SuppressWarnings("CheckStyle") private void compileAverageStats(StatsGraphFragment fragment, Cursor cursor, List<Entry> listEntries, List<String> listLabels) { Calendar lastEntryDate = null; Calendar lastLabelDate = null; Calendar currentDate = null; DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.CANADA); boolean addLabelOnDateChange = false; int numberOfGames = 0, totalPinfall = 0; int currentEntry = 0; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { Date entryDate = DateUtils.parseEntryDate(cursor.getString( cursor.getColumnIndex(SeriesEntry.COLUMN_SERIES_DATE))); if (entryDate == null) return; currentDate = DateUtils.getCalendarAtMidnight(entryDate); if (addLabelOnDateChange && currentDate.getTimeInMillis() != lastEntryDate.getTimeInMillis()) { addLabelOnDateChange = false; lastLabelDate = lastEntryDate; if (numberOfGames > 0) listEntries.add(new Entry(totalPinfall / numberOfGames, currentEntry)); else listEntries.add(new Entry(0, currentEntry)); listLabels.add(dateFormat.format(lastEntryDate.getTime())); currentEntry++; if (!fragment.mStatAccumulate) { numberOfGames = 0; totalPinfall = 0; } } if (lastLabelDate == null || lastLabelDate.getTimeInMillis() <= currentDate.getTimeInMillis() + DateUtils.MILLIS_ONE_WEEK) addLabelOnDateChange = true; lastEntryDate = currentDate; int gameNumber = cursor.getInt(cursor.getColumnIndex(GameEntry.COLUMN_GAME_NUMBER)); int frameNumber = cursor.getInt(cursor.getColumnIndex(FrameEntry.COLUMN_FRAME_NUMBER)); if (frameNumber == 1 && gameNumber - 1 == fragment.mStatIndex) { short gameScore = cursor.getShort(cursor.getColumnIndex(GameEntry.COLUMN_SCORE)); if (gameScore > 0) { numberOfGames++; totalPinfall += gameScore; } } cursor.moveToNext(); } } if (lastEntryDate != null && (lastLabelDate == null || currentDate.getTimeInMillis() != lastLabelDate.getTimeInMillis())) { if (numberOfGames > 0) listEntries.add(new Entry(totalPinfall / numberOfGames, currentEntry)); else listEntries.add(new Entry(0, currentEntry)); listLabels.add(dateFormat.format(lastEntryDate.getTime())); } } /** * Generates line chart data for match play stats. * * @param fragment parent fragment * @param cursor bowler / league data * @param listChanceEntries list of data entries for chances at increasing a stat * @param listSuccessEntries list of data entries for achieved stat values * @param listLabels list of labels for x axis */ @SuppressWarnings("CheckStyle") private void compileMatchPlayStats(StatsGraphFragment fragment, Cursor cursor, List<Entry> listChanceEntries, List<Entry> listSuccessEntries, List<String> listLabels) { Calendar lastEntryDate = null; Calendar lastLabelDate = null; Calendar currentDate = null; DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.CANADA); boolean addLabelOnDateChange = false; int matchResults = 0, totalMatchPlayGames = 0; int currentEntry = 0; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { Date entryDate = DateUtils.parseEntryDate(cursor.getString( cursor.getColumnIndex(SeriesEntry.COLUMN_SERIES_DATE))); if (entryDate == null) return; currentDate = DateUtils.getCalendarAtMidnight(entryDate); if (addLabelOnDateChange && currentDate.getTimeInMillis() != lastEntryDate.getTimeInMillis()) { addLabelOnDateChange = false; lastLabelDate = lastEntryDate; if (totalMatchPlayGames > 0) listSuccessEntries.add(new Entry(matchResults / totalMatchPlayGames, currentEntry)); else listSuccessEntries.add(new Entry(0, currentEntry)); listChanceEntries.add(new Entry(totalMatchPlayGames, currentEntry)); listLabels.add(dateFormat.format(lastEntryDate.getTime())); currentEntry++; if (!fragment.mStatAccumulate) { matchResults = 0; totalMatchPlayGames = 0; } } if (lastLabelDate == null || lastLabelDate.getTimeInMillis() <= currentDate.getTimeInMillis() + DateUtils.MILLIS_ONE_WEEK) addLabelOnDateChange = true; lastEntryDate = currentDate; int frameNumber = cursor.getInt(cursor.getColumnIndex( FrameEntry.COLUMN_FRAME_NUMBER)); if (frameNumber == 1) { int matchPlay = cursor.getInt(cursor.getColumnIndex(GameEntry.COLUMN_MATCH_PLAY)); if (matchPlay > 0) { totalMatchPlayGames++; if (matchPlay - 1 == fragment.mStatIndex) matchResults++; } } cursor.moveToNext(); } } if (lastEntryDate != null && (lastLabelDate == null || currentDate.getTimeInMillis() != lastLabelDate.getTimeInMillis())) { if (totalMatchPlayGames > 0) listSuccessEntries.add(new Entry(matchResults / totalMatchPlayGames, currentEntry)); else listSuccessEntries.add(new Entry(0, currentEntry)); listChanceEntries.add(new Entry(totalMatchPlayGames, currentEntry)); listLabels.add(dateFormat.format(lastEntryDate.getTime())); } } /** * Generates line chart data for overall stats. * * @param fragment parent fragment * @param cursor bowler / league data * @param listEntries list of data entries * @param listLabels list of labels for x axis */ @SuppressWarnings("CheckStyle") // I ain't even gonna bother private void compileOverallStats(StatsGraphFragment fragment, Cursor cursor, List<Entry> listEntries, List<String> listLabels) { Calendar lastEntryDate = null; Calendar lastLabelDate = null; Calendar currentDate = null; DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.CANADA); boolean addLabelOnDateChange = false; int numberOfGames = 0, totalPinfall = 0; int highSingle = 0, highSeries = 0, currentSeries = 0; if (fragment.mStatAccumulate) { long id = (mStatsToLoad == StatUtils.LOADING_LEAGUE_STATS) ? ((MainActivity) fragment.getActivity()).getLeagueId() : ((MainActivity) fragment.getActivity()).getBowlerId(); List<Short> baseAverages = new ArrayList<>(); List<Integer> baseGameCounts = new ArrayList<>(); StatUtils.getBaseAverages(fragment.getContext(), id, mStatsToLoad == StatUtils.LOADING_LEAGUE_STATS, baseAverages, baseGameCounts); // Add the base averages from the leagues to the stats for (int i = 0; i < baseAverages.size() && i < baseGameCounts.size(); i++) { totalPinfall += baseAverages.get(i) * baseGameCounts.get(i); numberOfGames += baseGameCounts.get(i); } } int currentEntry = 0; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { Date entryDate = DateUtils.parseEntryDate(cursor.getString( cursor.getColumnIndex(SeriesEntry.COLUMN_SERIES_DATE))); if (entryDate == null) return; currentDate = DateUtils.getCalendarAtMidnight(entryDate); if (addLabelOnDateChange && currentDate.getTimeInMillis() != lastEntryDate.getTimeInMillis()) { if (currentSeries > highSeries) highSeries = currentSeries; addLabelOnDateChange = false; lastLabelDate = lastEntryDate; Entry entry; switch (fragment.mStatIndex) { case StatUtils.STAT_AVERAGE: if (numberOfGames > 0) entry = new Entry(totalPinfall / numberOfGames, currentEntry); else entry = new Entry(0, currentEntry); break; case StatUtils.STAT_HIGH_SINGLE: entry = new Entry(highSingle, currentEntry); break; case StatUtils.STAT_HIGH_SERIES: entry = new Entry(highSeries, currentEntry); break; case StatUtils.STAT_TOTAL_PINS: entry = new Entry(totalPinfall, currentEntry); break; case StatUtils.STAT_NUMBER_OF_GAMES: entry = new Entry(numberOfGames, currentEntry); break; default: entry = null; } if (entry != null) { listEntries.add(entry); listLabels.add(dateFormat.format(lastEntryDate.getTime())); currentEntry++; } if (!fragment.mStatAccumulate) { numberOfGames = 0; totalPinfall = 0; highSeries = 0; highSingle = 0; currentSeries = 0; } } if (lastLabelDate == null || lastLabelDate.getTimeInMillis() <= currentDate.getTimeInMillis() + DateUtils.MILLIS_ONE_WEEK) addLabelOnDateChange = true; lastEntryDate = currentDate; int gameNumber = cursor.getInt(cursor.getColumnIndex(GameEntry.COLUMN_GAME_NUMBER)); int frameNumber = cursor.getInt(cursor.getColumnIndex(FrameEntry.COLUMN_FRAME_NUMBER)); if (frameNumber == 1) { if (gameNumber == 1) { if (currentSeries > highSeries) highSeries = currentSeries; currentSeries = 0; } short gameScore = cursor.getShort(cursor.getColumnIndex(GameEntry.COLUMN_SCORE)); if (gameScore > 0) { numberOfGames++; totalPinfall += gameScore; currentSeries += gameScore; if (gameScore > highSingle) highSingle = gameScore; } } cursor.moveToNext(); } } if (lastEntryDate != null && (lastLabelDate == null || currentDate.getTimeInMillis() != lastLabelDate.getTimeInMillis())) { if (numberOfGames > 0) listEntries.add(new Entry(totalPinfall / numberOfGames, currentEntry)); else listEntries.add(new Entry(0, currentEntry)); listLabels.add(dateFormat.format(lastEntryDate.getTime())); } } } /** * Returns a cursor from database to load either bowler or league stats. * * @param shouldGetLeagueStats if true, league stats will be loaded. Bowler stats will be loaded otherwise * @return a cursor with rows relevant to mBowlerId or mLeagueId */ private Cursor getBowlerOrLeagueCursor(boolean shouldGetLeagueStats) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); boolean isEventIncluded = preferences.getBoolean(Constants.KEY_INCLUDE_EVENTS, true); boolean isOpenIncluded = preferences.getBoolean(Constants.KEY_INCLUDE_OPEN, true); SQLiteDatabase database = DatabaseHelper.getInstance(getContext()).getReadableDatabase(); String rawStatsQuery = "SELECT " + SeriesEntry.COLUMN_SERIES_DATE + ", " + GameEntry.COLUMN_SCORE + ", " + GameEntry.COLUMN_GAME_NUMBER + ", " + GameEntry.COLUMN_IS_MANUAL + ", " + GameEntry.COLUMN_MATCH_PLAY + ", " + FrameEntry.COLUMN_FRAME_NUMBER + ", " + FrameEntry.COLUMN_IS_ACCESSED + ", " + FrameEntry.COLUMN_FOULS + ", " + FrameEntry.COLUMN_PIN_STATE[0] + ", " + FrameEntry.COLUMN_PIN_STATE[1] + ", " + FrameEntry.COLUMN_PIN_STATE[2] + " FROM " + LeagueEntry.TABLE_NAME + " AS league" + " INNER JOIN " + SeriesEntry.TABLE_NAME + " AS series" + " ON league." + LeagueEntry._ID + "=series." + SeriesEntry.COLUMN_LEAGUE_ID + " INNER JOIN " + GameEntry.TABLE_NAME + " AS game" + " ON series." + SeriesEntry._ID + "=game." + GameEntry.COLUMN_SERIES_ID + " INNER JOIN " + FrameEntry.TABLE_NAME + " AS frame" + " ON game." + GameEntry._ID + "=frame." + FrameEntry.COLUMN_GAME_ID + ((shouldGetLeagueStats) ? " WHERE league." + LeagueEntry._ID + "=?" : " WHERE league." + LeagueEntry.COLUMN_BOWLER_ID + "=?") + " AND " + ((!shouldGetLeagueStats && !isEventIncluded) ? LeagueEntry.COLUMN_IS_EVENT : "'0'") + "=?" + " AND " + ((!shouldGetLeagueStats && !isOpenIncluded) ? LeagueEntry.COLUMN_LEAGUE_NAME + "!" : "'0'") + "=?" + " ORDER BY series." + SeriesEntry.COLUMN_SERIES_DATE + ", series." + SeriesEntry._ID + ", game." + GameEntry.COLUMN_GAME_NUMBER + ", frame." + FrameEntry.COLUMN_FRAME_NUMBER; String[] rawStatsArgs = { ((shouldGetLeagueStats) ? String.valueOf(((MainActivity) getActivity()).getLeagueId()) : String.valueOf(((MainActivity) getActivity()).getBowlerId())), String.valueOf(0), ((!shouldGetLeagueStats && !isOpenIncluded) ? Constants.NAME_OPEN_LEAGUE : String.valueOf(0)) }; return database.rawQuery(rawStatsQuery, rawStatsArgs); } }